查看原文
其他

你可能没有实现一个正确的atoi函数

守望先生 编程珠玑 2021-02-01

前言

我们都知道,atoi函数用于将一个字符串转换成整数。atoi函数看起来似乎很容易实现,你甚至可以很快写出一个版本,但是是否符合要求呢?

简易版本

最简单的考虑,就是遍历字符串,每遇到一个数字就加上原来的值乘以10。例如字符串“1234”转整数是这样的计算流程:

  • 遇到字符1,得到结果1;

  • 遇到字符2,得到结果1 * 10 + 2,即12;

  • 遇到字符3,得到结果12 * 10 + 3,即123;

  • 遇到字符4,得到结果123 * 10 + 4,即1234;

代码实现如下:

int my_atoi(const char *str)
{
    if(NULL == str)
        return 0;
    int ret = 0;
    while(0 != *str)
    {
        ret = ret * 10 + *str - '0';
        str++;
    }
    return ret;
}

看起来既简洁又没有什么问题,输入数值时也似乎能得到正确结果。真的是这样吗?如果传入以下字符串参数,会是什么结果呢?

"-1"
"+1"
"  "
"111111111111"
""
"1aab"

是不是发现并不是想象中的那样?那么实现atoi到底需要注意什么呢?

实现atoi函数需要注意什么

你可能已经注意到了,实现atoi需要考虑下面这些场景:

  • 输入正负号

  • 开头有空格

  • 转换后的数值超出int的表示范围

  • 出错时返回0与正确转换0的区别

  • 输入非数字

  • 空字符串

现在来看,前面的实现还能满足要求吗?

再次实现

那么重新考虑上面的要求,我们如何实现呢?我们需要考虑以下几种情况

  • 如果开头是负号,则标记为负数;正号或数值,则标记为正数

  • 跳过开头的空格,从第一个有效字符开始

  • 使用更大类型存储计算值,如果负数比INT_MIN还小或正数比INT_MAX还大,则表明溢出,返回INT_MIN或INT_MAX,或者在下次计算之前与INT_MIN/10或INT_MAX/10比较

  • 使用全局变量记录出错情况,区别正常转换为0或最大最小值

  • 遇到非数值时即退出

根据上面这些考虑,我们重新实现代码:

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <limits.h>
#define NULL_PTR_ERR 1
#define ERANGE 2
int errCode = 0;
int my_atoi(char* str) {
    int negative = 0;
    char c = 0;
    /*计算结果存储*/
    long long ret = 0;
    errCode = 0;
    if(NULL == str)
    {
        errCode = NULL_PTR_ERR;
        return 0;
    }
    /*跳过开始的空格*/
    while(' ' == (*str))
        str++;
    /*跳过空格之后,到达了字符串结尾,则退出*/
    if(0 == *str)
        return 0;
    /*负数*/
    if(*str == '-' )
    {
        negative = 1;
        str++;
    }
    /*正数*/
    else if(*str == '+')
    {
        negative = 0;
        str++;
    }
    /*正数*/
    else if(isdigit(*str))
    {
        negative = 0;
    }
    /*如果不是以上内容,则直接退出*/
    else
    {
        return 0;
    }
    while(isdigit(*str))
    {
        /*计算结果*/
        ret = ret*10 + *str -'0';

        /*如果发现结果大于INT_MAX或者小于INT_MIN,则溢出,返回最值*/
        if(ret > (negative?-(long long )INT_MIN:INT_MAX))
        {
            /*溢出,返回最大值*/
            errCode = ERANGE;
            return negative?INT_MIN:INT_MAX;
        }
        str++;
    }
    /*根据正负号返回正确的结果*/
    return negative?-ret:ret;
}
int main(void)
{
    /*只有一个负号*/
    int result = my_atoi("-");
    printf("-:%d,errCode:%d\n",result,errCode);

    /*空指针*/
    result = my_atoi(NULL);
    printf("NULL:%d,errCode:%d\n",result,errCode);
    /*空字符串*/
    result = my_atoi("      ");
    printf("     :%d,errCode:%d\n",result,errCode);

    /*负数*/
    result = my_atoi("-1");
    printf("  -1:%d,errCode:%d\n",result,errCode);

    /*负数溢出*/
    result = my_atoi("   -11111111111");
    printf("   -11111111111:%d,errCode:%d\n",result,errCode);

    /*正数*/
    result = my_atoi("+123");
    printf("+123:%d,errCode:%d\n",result,errCode);

     /*正数溢出*/
    result = my_atoi("+123111111111111111");
    printf("+123111111111111111:%d,errCode:%d\n",result,errCode);
    return 0;
}

运行结果:

-:0,errCode:0
NULL:0,errCode:1
     :0,errCode:0
  -1:-1,errCode:0
   -11111111111:-2147483648,errCode:2
+123:123,errCode:0
+123111111111111111:2147483647,errCode:2

总结

上面的代码中errCode的设置需要根据需求而定,例如如果认为空字符串或只有负号转换是非法的,那么前面的代码将不符合要求。但这些都不是重点,重点是我们在考虑实现atoi函数的时候,需要考虑多种异常场景,这在平常实现其他功能接口的时候也是一样的。

思考

前面的代码有什么不足?你忽略了哪些场景?


关注公众号【编程珠玑】,获取更多Linux/C/C++/Python/Go/算法/工具等原创技术文章。后台免费获取经典电子书和视频资源。


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存